/*
	Copyright (c) 2008, Grant Jones <grant@podtronics.com>.
	All rights reserved.

	Redistribution and use in source and binary forms, with or
	without modification, are permitted provided that the following
	conditions are met:

		Redistributions of source code must retain the above copyright
		notice, this list of conditions and the following disclaimer.
		
		Redistributions in binary form must reproduce the above copyright
		notice, this list of conditions and the following disclaimer in
		the documentation and/or other materials provided with the
		distribution.
		
		Neither the name of the gxjones.com nor the names of its
		contributors may be used to endorse or promote products derived
		from this software without specific prior written permission.
	
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
	FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
	COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
	OF THE POSSIBILITY OF SUCH DAMAGE.
	
	http://www.opensource.org/licenses/bsd-license.php
*/

/*
 *  edb_net_main.c
 *  edb
 *
 *  Created by Grant Jones on 3/20/08.
 *
 */


#include "edb_main.h"
#include "edb_net_main.h"
#include "edb_net_shell.h"

#ifndef NO_REST_HTTP_SERVER
#include "edb_net_rest.h"
#endif

/* ************************************************************************************
************************************************************************************ */
/*!
	@function edb_net_on_timer
	@abstract called periodically with the time delta between calls
	@discussion currently unused
	@param fd
	@param ev
	@param arg
*/
void edb_net_on_timer( int fd, short ev, void *arg )
{
	// called periodically with the actual delta time between calls:
/*	ProgramContextStruct *pcs = (ProgramContextStruct *)arg;*/
	float update_delta;

	// get the new time delta:
	read( fd, &update_delta, sizeof(float) );	
	

	// pump the server:

	// onTick
}


/* ************************************************************************************
************************************************************************************ */
/*!
	@function edb_getMSCount
	@abstract returns the number of ms since Jan 1, 1970
	@discussion
	@result a double representing the number of ms since the epoch
*/
double edb_getMSCount(void)
{
	struct timeval t;
	gettimeofday(&t, NULL);
	return ((double)t.tv_sec * 1000000.0 + (double)t.tv_usec) / 1000.0;
} 

/*!
	@function edb_net_timer_thread
	@abstract periodically writes a time delta to a socket
	@discussion
	@param timer_fd_ptr pointer to the timer file desc. it should write to
	@result never returns, but if it ever did returns NULL
*/	
void *edb_net_timer_thread( void *timer_fd_ptr )
{
	int timer_fd = *((int *)timer_fd_ptr);
	double last_update = edb_getMSCount();
	double time_current = 0;
	float time_delta = 0;
	
	while(1)
	{
		// wait a bit and do it all again:
//		usleep( 1000000 );
		usleep( 1000000 );
		
		// calc delta time, send to main thread:
		time_current = edb_getMSCount();
		time_delta = time_current - last_update;
		last_update = time_current;

		write( timer_fd, &time_delta, sizeof(float) );
	}
	
	return NULL;
}


/* ************************************************************************************
************************************************************************************ */

int edb_net_main( ProgramContextStruct *pcs, int argc, char * const argv[])
{
	int listen_fd;
	struct sockaddr_in listen_addr;
	struct event ev_accept;
	struct event ev_timermsg;
	int reuseaddr_on;
	int timer_fds[2];
	
	/* Allocate a new client map: */
	pcs->clients = cmap_new(NULL);

	
	/* Initialize libevent. */
	pcs->net_evbase = event_init();
	
	/* Create socket pair for communication btw timer thread and net thread */
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, timer_fds) != 0)
		err(1, "socketpair failed");
		
	/* Create our listening socket. */
	listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_fd < 0)
	{
		err(1, "listen failed");
	}
	
	reuseaddr_on = 1;
	setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on));

	{
	    struct linger ling = {0, 0};
		int flags =1;
		
		setsockopt(listen_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
		setsockopt(listen_fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
//		setsockopt(listen_fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
	}


	memset(&listen_addr, 0, sizeof(listen_addr));
	listen_addr.sin_family = AF_INET;
	listen_addr.sin_addr.s_addr = pcs->net_shell_addr.s_addr;
	listen_addr.sin_port = htons(pcs->net_shell_port);

	if (bind(listen_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0)
	        err(1, "bind failed");
			
	if (listen(listen_fd, 5) < 0)
	        err(1, "listen failed");

	/* Set the socket to non-blocking, this is essential in event
	 * based programming with libevent. */
	if (edb_setnonblock(listen_fd) < 0)
	        err(1, "failed to set server socket to non-blocking");

	/* We now have a listening socket, we create a read event to
	 * be notified when a client connects. */
	event_set(&ev_accept, listen_fd, EV_READ|EV_PERSIST, edb_net_shell_accept, pcs);
	event_base_set( pcs->net_evbase, &ev_accept ); // event base is main network thread
	event_add(&ev_accept, NULL);

	// Create a new event for communications from timer thread to main network thread
	// net ctx:
	event_set(&ev_timermsg, timer_fds[0], EV_READ|EV_PERSIST, edb_net_on_timer, pcs);
	event_base_set( pcs->net_evbase, &ev_timermsg ); // event base is main thread
	event_add(&ev_timermsg, NULL);

	pthread_t tid;

	/* spawn a timer thread */
#define USE_TIMER_THREAD
#ifdef USE_TIMER_THREAD	
	pthread_create( &tid, NULL, edb_net_timer_thread, &timer_fds[1] );
#endif


#ifndef NO_REST_HTTP_SERVER
	
	edb_net_rest_init(pcs);
	
	pcs->http_srv = evhttp_new( pcs->net_evbase );
	if( pcs->http_srv )
	{
		evhttp_set_gencb( pcs->http_srv, edb_net_rest_req, pcs );
		
		/* TODO: 0.0.0.0 needs to have override option from command-line cfg */
		evhttp_bind_socket( pcs->http_srv, "0.0.0.0", pcs->net_http_port );
	}
#endif	

	/* Start the event loop. */
	event_base_loop(pcs->net_evbase, 0);


    return 0;
}



